/**
 * gcog - generate code with inlined Groovy code.
 * http://code.google.com/p/gcog/
 * 
 * Copyright 2008, Tobias Hientzsch.
 * 
 * MIT license http://www.opensource.org/licenses/mit-license.php
 * 
 * Original idea and code from Cog code generation tool.
 * http://nedbatchelder.com/code/cog
 *
 * Copyright 2004-2008, Ned Batchelder.
 * 
 */

public class GCog {
	def sBeginSpec
	def sEndSpec
	def sEndOutput
	def sEndFormat
	def sOutputMode
	def reEndOutput
	def options
	def GCog() {
		        sBeginSpec = '[[[gcog'
		        sEndSpec = ']]]'
		        sEndOutput = '[[[end]]]'
//		        reEndOutput = /\[\[\[end]]](?P<hashsect> *\(checksum: (?P<hash>[a-f0-9]+)\))/
//		        sEndFormat = '[[[end]]] (checksum: %s)'

		        //options = CogOptions()
		        options = [:]
//		        sOutputMode = 'w'

		        //installCogModule()

	}
    def showWarning(msg) {
        println "Warning:", msg
    }

    def isBeginSpecLine(String s) {
        return s.indexOf(sBeginSpec) >= 0
    }

    def isEndSpecLine(String s) {
        return s.indexOf(sEndSpec) >= 0 && !isEndOutputLine(s)
    }

    def isEndOutputLine(String s) {
        return s.indexOf(sEndOutput) >= 0
    }
    
    def processString(fIn , fname=null) {
    	def fOut = new StringWriter();
    	process(new StringReader(fIn), fOut, fname);
    	return fOut.toString()
    }

    void processFile(fIn, fOut, fname=null) {
    	fOut = new FileWriter(new File(fOut));
    	fIn = new FileReader(new File(fIn));
    	process(fIn, fOut, fname);
    	fOut.flush();
    	fOut.close();
    }

    def processOneFile(fInOut , fname=null) {
    	def fIn = new File(fInOut).getText();
    	def fOut = processString(fIn, fInOut);
    	if(fIn != fOut) {
    		fOut = new File(fInOut).write(fOut)
    	}
    }

    def process(fIn, Writer fOut, fname=null) {
    	fIn = new NumberedFileReader(fIn)
    	def sFileIn = fname || ''
    	
    	def bSawCog = false
    	
        // loop over generator chunks
        def l = fIn.readLine()
        def firstLine = true
        def write = { s ->
        	if(firstLine){
	        	firstLine = false
        	} else {  
        		s = '\n' + s
        	}
        		
        	fOut.write(s)
        }
        while(l!=null) {
            // Find the next spec begin
            while(l!=null && !isBeginSpecLine(l)) {
                if (isEndSpecLine(l)) {
                    throw new Exception("Unexpected '$sEndSpec'" /*% self.sEndSpec,
                        file=sFileIn, line=fIn.linenumber()*/)
                }
                if (isEndOutputLine(l)) {
                	throw new Exception("Unexpected '$sEndOutput'" /*% self.sEndOutput,
                        file=sFileIn, line=fIn.linenumber()*/)
                }
                write(l)
                l = fIn.readLine()
            }
            if(l==null)
                break
            if(!options.bDeleteCode) {
                write(l)
            }
            // l is the begin spec
            def gen = new GCogGenerator()
            //gen.setOutput(stdout=self.stdout)
            gen.parseMarker(l)
            def firstLineNum = fIn.linenumber()
            
            // If the spec begin is also a spec end, then process the single
            // line of code inside.
            if(isEndSpecLine(l)) {
                def beg = l.indexOf(sBeginSpec)
                def end = l.indexOf(sEndSpec)
                if(beg > end && end >=0) {
                    throw new Exception("Cog code markers inverted");
                        //file=sFileIn, line=firstLineNum)
                } else {
                    def sCode = l[beg+sBeginSpec.length() .. end-1].trim()
                    gen.parseLine(sCode)
                }
            } else {
                // Deal with an ordinary code block.
                l = fIn.readLine()
    
                // Get all the lines in the spec
                while(l!=null && !isEndSpecLine(l)) {
                    if(isBeginSpecLine(l)){
                        throw new Exception("Unexpected '$sBeginSpec'") /*% self.,
                           file=sFileIn, line=fIn.linenumber())*/
                    }
                    if(isEndOutputLine(l)) {
                        throw new Exception("Unexpected '$sEndOutput'") /* % self.sEndOutput,
                            file=sFileIn, line=fIn.linenumber())*/
                    }
                    if(!options.bDeleteCode) {
                        write(l)
                    }
                    gen.parseLine(l)
                    l = fIn.readLine()
                }
                if (l==null) {
                    new Exception(
                        "Cog block begun but never ended.");/*,
                        file=sFileIn, line=firstLineNum)*/
                }
                if(!options.bDeleteCode) {
                    write(l)
                }
                gen.parseMarker(l)
            }
            l = fIn.readLine()
            
            // Eat all the lines in the output section.  While reading past
            // them, compute the md5 hash of the old output.
            //hasher = md5.new()
            while(l!=null && !isEndOutputLine(l)) {
                if (isBeginSpecLine(l)){
                	throw new Exception("Unexpected '$sBeginSpec'")/* % self.sBeginSpec,
                        file=sFileIn, line=fIn.linenumber())*/
                }
                if(isEndSpecLine(l)) {
                    throw new Exception("Unexpected '$sEndSpec'") /* % self.sEndSpec,
                        file=sFileIn, line=fIn.linenumber())*/
                }
                //hasher.update(l)
                l = fIn.readLine()
            }
            //curHash = hasher.hexdigest()

            if(l==null && !options.bEofCanBeEnd) {
                // We reached end of file before we found the end output line.
                throw new Exception("Missing '$sEndOutput' before end of file.")/* % self.sEndOutput,
                    file=sFileIn, line=fIn.linenumber())*/
            }
            // Write the output of the spec to be the new output if we're 
            // supposed to generate code.
            
            //hasher = md5.new()
            if(!options.bNoGenerate) {
                def sFile = "$sFileIn+$firstLineNum"
                def globals = []
                def sGen = gen.evaluate(this, globals, fname=sFile)
                //sGen = suffixLines(sGen)
                //hasher.update(sGen)
               	if(sGen != '') {
               		write(sGen)
                	firstLine = true //TODO: get rid of this workaround 
               	}
            }
            //newHash = hasher.hexdigest()
    
             bSawCog = true
             /*
             // Write the ending output line
             hashMatch = self.reEndOutput.search(l)
             if self.options.bHashOutput:
                 if hashMatch:
                     oldHash = hashMatch.groupdict()['hash']
                     if oldHash != curHash:
                         raise CogError("Output has been edited! Delete old checksum to unprotect.",
                             file=sFileIn, line=fIn.linenumber())
                     # Create a new end line with the correct hash.
                     endpieces = l.split(hashMatch.group(0), 1)
                 else:
                     # There was no old hash, but we want a new hash.
                     endpieces = l.split(self.sEndOutput, 1)
                 l = (self.sEndFormat % newHash).join(endpieces)
             else:
                 # We don't want hashes output, so if there was one, get rid of
                 # it.
                 if hashMatch:
                     l = l.replace(hashMatch.groupdict()['hashsect'], '', 1)
             */        

            if(!options.bDeleteCode) {
                write(l)
            }
            l = fIn.readLine()
        }
         if(!bSawCog &&  options.bWarnEmpty) {
             println "no cog code found in $sFileIn"
         }

    }
    /*
    // A regex for non-empty lines, used by suffixLines.
    def reNonEmptyLines = re.compile(/^\s*\S+.*$/, re.MULTILINE)
    
    def suffixLines(text) {
        """ Add suffixes to the lines in text, if our options desire it.
            text is many lines, as a single string.
        """
        if(options.sSuffix) {
            // Find all non-blank lines, and add the suffix to the end.
            repl = /\g<0>/ + options.sSuffix.replaceAll('\\', '\\\\')
            text = self.reNonEmptyLines.sub(repl, text)
        }
        return text
    }
    */ 
	/**
	 * @param args
	 */
	static def void main(String[] args){
		def c = new GCog()
		if(args.size()> 0) {
			def f = args[0]
			c.processOneFile(f,f)
		}
	}
	
}
